/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[欢迎加盟知启蒙，一起邂逅框架梦]
 * 
 * https://www.zhiqim.com/gitcan/zhiqim/zhiqim_account.htm
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zhiqim.account.api.pay;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.zhiqim.account.ZacConstants;
import org.zhiqim.account.dbo.user.UserAccount;
import org.zhiqim.account.dbo.user.UserTradeWxpay;
import org.zhiqim.kernel.Global;
import org.zhiqim.kernel.Servicer;
import org.zhiqim.kernel.config.Group;
import org.zhiqim.kernel.constants.CodeConstants;
import org.zhiqim.kernel.extend.HashMapSS;
import org.zhiqim.kernel.extend.MapSS;
import org.zhiqim.kernel.extend.TreeMapSS;
import org.zhiqim.kernel.httpclient.HttpPost;
import org.zhiqim.kernel.json.Jsons;
import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Ids;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Validates;
import org.zhiqim.kernel.util.codes.MD5;
import org.zhiqim.kernel.xml.Xmls;
import org.zhiqim.orm.ZTable;
import org.zhiqim.orm.dbo.Selector;

/**
 * 微信支付服务
 *
 * @version v1.4.1 @author zouzhigang 2018-4-24 新建与整理
 */
public class WxpayServer extends Servicer implements ZacConstants, CodeConstants
{
    private static final Log log = LogFactory.getLog(WxpayServer.class);
    
    private String appId;               //公众账号ID
    private String mchId;               //商户号
    private String apiKey;              //接口密钥
    private String notifyUrl;           //提示消息返回地址
    private String websiteUrl;          //网站地址
    private String websiteName;         //网站名称
    
    /****************************************************************************************/
    //获取属性
    /****************************************************************************************/
    
    public boolean create() throws Exception
    {
        Group group = Global.getGroup(id);
        if (group == null)
        {
            log.error("微信支付服务[%s]未找到配置", id);
            return false;
        }
        
        this.appId = group.getString("appId");
        if (Validates.isEmpty(this.appId))
        {
            log.error("微信支付服务[%s][appId]不能为空", id);
            return false;
        }
        
        this.mchId = group.getString("mchId");
        if (Validates.isEmpty(this.mchId))
        {
            log.error("微信支付服务[%s][mchId]不能为空", id);
            return false;
        }
        
        this.apiKey = group.getString("apiKey");
        if (Validates.isEmpty(this.apiKey))
        {
            log.error("微信支付服务[%s][apiKey]不能为空", id);
            return false;
        }
        
        this.notifyUrl = group.getString("notifyUrl");
        if (Validates.isEmpty(this.notifyUrl))
        {
            log.error("微信支付服务[%s][notifyUrl]不能为空", id);
            return false;
        }
        
        this.websiteUrl = group.getString("websiteUrl");
        if (Validates.isEmpty(this.websiteUrl))
        {
            log.error("微信支付服务[%s][websiteUrl]不能为空", id);
            return false;
        }
        
        this.websiteName = group.getString("websiteName");
        if (Validates.isEmpty(this.websiteName))
        {
            log.error("微信支付服务[%s][websiteName]不能为空", id);
            return false;
        }
        
        return true;
    }
    
    /****************************************************************************************/
    //API
    /****************************************************************************************/
    
    /**
     * 统一下单，https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
     * 
     * @param trade             交易对象
     * @param spbillCreateIp    下单IP地址
     * @return                  结果参数表
     * @throws Exception
     */
    public TreeMapSS unifiedorder(UserTradeWxpay trade, String spbillCreateIp) throws Exception 
    {
        //组装数据
        TreeMapSS map = new TreeMapSS();
        map.setXmlTag("xml");
        map.put("nonce_str", trade.getTradeNonceStr());
        map.put("body", trade.getTradeSubject());
        map.put("out_trade_no", Strings.toString(trade.getTradeId()));
        map.put("total_fee", Strings.toString(trade.getTradeAmount()));
        map.put("spbill_create_ip", spbillCreateIp);
        map.put("trade_type", trade.getTradeType());
        
        if (WXPAY_MWEB.equals(trade.getTradeType()))
        {//移动端调用H5支付,增加scene_info
            Map<String, String> temp = new HashMap<String, String>();
            temp.put("type", "Wap");
            temp.put("wap_url", websiteUrl);
            temp.put("wap_name", websiteName);
            
            String json = Jsons.toString(temp);
            Map<String, String> temp2 = new HashMap<String, String>();
            temp2.put("h5_info", json);
            
            String sceneInfo = Jsons.toString(temp2);
            
            map.put("scene_info", sceneInfo);
        }
        else if (WXPAY_JSAPI.equals(trade.getTradeType()))
        {
            UserAccount connect = Global.get(ZTable.class).item(UserAccount.class, new Selector("userId", trade.getUserId()).addMust("accountType", ACCOUNT_TYPE_WXMP));
            Asserts.assertNotNull(connect, "非公众号登录的用户不支持使用公众号支付");
            
            map.put("openid", connect.getUserAccount());
        }
        
        map.put("appid", appId);
        map.put("mch_id", mchId);
        map.put("notify_url", notifyUrl);
        map.put("sign", buildSignature(map));
        
        String xml = Xmls.toString(map);
        HttpPost conn = new HttpPost(WXPAY_UNIFIEDORDER_URL);
        conn.setConnectionClose();
        conn.setConnectTimeout(10);
        conn.setReadTimeout(30);
        conn.setQueryString(xml, _UTF_8_);
        conn.execute();

        return Xmls.toObject(conn.getResponseText(), TreeMapSS.class);
    }
    
    /**
     * 查询订单，https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_2&index=2
     * 
     * @param tradeId           交易编号
     * @param nonceStr          随机字符串
     * @return
     * @throws Exception
     */
    public MapSS orderquery(long tradeId) throws Exception
    {
        TreeMapSS map = new TreeMapSS();
        map.setXmlTag("xml");
        map.put("appid", appId);
        map.put("mch_id", mchId);
        map.put("out_trade_no", Strings.toString(tradeId));
        map.put("nonce_str", Ids.uuid());
        map.put("sign", buildSignature(map));
        
        String xml = Xmls.toString(map);
        HttpPost conn = new HttpPost(WXPAY_ORDERQUERY_URL);
        conn.setConnectionClose();
        conn.setConnectTimeout(10);
        conn.setReadTimeout(30);
        conn.setQueryString(xml, _UTF_8_);
        conn.execute();

        return Xmls.toObject(conn.getResponseText(), HashMapSS.class);
    }
    
    /**
     * 关闭订单，https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_3
     * 
     * @param tradeId           交易编号
     * @return                  结果参数表
     * @throws Exception        可能的异常
     */
    public MapSS closeorder(long tradeId) throws Exception
    {
        TreeMapSS map = new TreeMapSS();
        map.setXmlTag("xml");
        map.put("appid", appId);
        map.put("mch_id", mchId);
        map.put("out_trade_no", Strings.toString(tradeId));
        map.put("nonce_str", Ids.uuid());
        map.put("sign", buildSignature(map));
        
        String xml = Xmls.toString(map);
        HttpPost conn = new HttpPost(WXPAY_CLOSEORDER_URL);
        conn.setConnectionClose();
        conn.setConnectTimeout(10);
        conn.setReadTimeout(30);
        conn.setQueryString(xml, _UTF_8_);
        conn.execute();
        
        return Xmls.toObject(conn.getResponseText(), HashMapSS.class);
    }

    /****************************************************************************************/
    //签名&验证
    /****************************************************************************************/
    
    /**
     * 生成签名
     * 
     * @param data      数据表
     * @return          生成的签名字符串
     */
    public String buildSignature(TreeMapSS data)
    {
        StringBuilder strb = new StringBuilder();
        for (Entry<String, String> entry : data.entrySet())
        {
            if (WXPAY_FIELD_SIGN.equals(entry.getKey()))
                continue;
            
            strb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        strb.append("key=").append(apiKey);
        
        return MD5.encodeUTF8(strb.toString()).toUpperCase();
    }
    
    /**
     * 验证签名有效性
     * 
     * @param data          数据表
     * @return              =true表示成功
     */
    public boolean chkSignature(TreeMapSS data)
    {
        if (!data.containsKey(WXPAY_FIELD_SIGN))
            return false;
        
        return buildSignature(data).equals(data.get(WXPAY_FIELD_SIGN));
    }
    
    /****************************************************************************************/
    //获取属性
    /****************************************************************************************/
    
    public String getAppId()
    {
        return appId;
    }

    public String getMchId()
    {
        return mchId;
    }

    public String getApiKey()
    {
        return apiKey;
    }

    public String getNotifyUrl()
    {
        return notifyUrl;
    }

    public String getWebsiteUrl()
    {
        return websiteUrl;
    }

    public String getWebsiteName()
    {
        return websiteName;
    }
    
}
